package yun.web.springmvc.ext;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import yun.web.enjoy.template.Engine;
import yun.web.enjoy.template.Template;
import yun.web.enjoy.template.source.ISource;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class View {
    String vp;

    // viewpath不为null data 作为模板变量
    //
    Map<String, Object> data;

    // 响应json时
    // 优先写 odata
    // odata可以不是map, 但在重定向时,
    Object odata;
    Map<String, String> headers;
    List<Cookie> cookies;

    String downloadFileName;
    File downloadFile;
    InputStream downloadInputStream;

    String previewFileName;
    File previewFile;
    InputStream previewInputStream;

    String qrCodeURL;
    int qrCodeWidth;
    int qrCodeHeight;


    public View clear() {
        this.odata = null;
        this.headers = null;
        this.cookies = null;
        this.downloadFileName = null;
        this.downloadFile = null;
        this.downloadInputStream = null;
        this.previewFileName = null;
        this.previewFile = null;
        this.previewInputStream = null;
        this.qrCodeURL = null;
        return this;
    }

    public View set(String k, Object v) {
        if (data == null) data = new HashMap<>();
        data.put(k, v);
        return this;
    }

    public View remove(String k) {
        if (this.data != null) data.remove(k);
        return this;
    }

    public View set(Map map) {
        this.data = map;
        return this;
    }

    public View set(Object odata) {
        this.odata = odata;
        return this;
    }

    public View cookie(String k, Object v) {
        if (cookies == null) {
            cookies = new ArrayList<>();
        }
        cookies.add(new Cookie(k, v == null ? null : v.toString()));
        return this;
    }

    public static View build() {
        return new View();
    }

    public static View ok() {
        return new View().set("code", 0);
    }

    public static View ok(String msg) {
        return new View().set("code", 0).set("msg", msg);
    }

    public static View fail() {
        return new View().set("code", 1);
    }

    public static View fail(String msg) {
        return new View().set("code", 1).set("msg", msg);
    }

    public View cookie(String k, Object v, int expire) {
        if (cookies == null) {
            cookies = new ArrayList<>();
        }
        Cookie cookie = new Cookie(k, v == null ? null : v.toString());
        cookie.setMaxAge(expire);
        cookies.add(cookie);
        return this;
    }


    public View cookie(Cookie cookie) {
        if (cookies == null) {
            cookies = new ArrayList<>();
        }
        cookies.add(cookie);
        return this;
    }


    public View header(String k, Object v) {
        if (headers == null) {
            headers = new HashMap<>();
        }
        headers.put(k, v.toString());
        return this;
    }

    public View download(File file, String name) {
        this.downloadFile = file;
        this.downloadFileName = name;
        return this;
    }

    public View download(InputStream inputStream, String name) {
        this.downloadInputStream = inputStream;
        this.downloadFileName = name;
        return this;
    }

    public View preview(File file, String name) {
        this.previewFile = file;
        this.previewFileName = name;
        return this;
    }

    public View preview(InputStream inputStream, String name) {
        this.previewInputStream = inputStream;
        this.previewFileName = name;
        return this;
    }

    public View qrCode(String url, int width, int height) {
        qrCodeURL = url;
        qrCodeWidth = width;
        qrCodeHeight = height;
        return this;
    }

    public View setVp(String vp) {
        this.vp = vp;
        return this;
    }


    public static View view(String viewPath) {
        View view = new View();
        view.vp = viewPath;
        return view;
    }

    void writeHeaders(HttpServletResponse resp) {
        if (headers != null) {
            headers.forEach(resp::setHeader);
        }
    }

    void writeCookies(HttpServletResponse resp) {
        if (cookies != null) {
            cookies.forEach((cookie) -> {
                // 若值为null, 设置为过期
                if (cookie.getValue() == null) {
                    cookie.setMaxAge(0);
                }
                resp.addCookie(cookie);
            });
        }
    }

    void writeDownloadFile(HttpServletResponse resp) throws IOException {
        String disposition = StrUtil.format("attachment;downloadFileName={}", URLUtil.encode(downloadFileName, "UTF-8"));
        resp.setHeader("Content-Disposition", disposition);
        resp.setHeader("Content-Type", "application/octet-stream");
        FileUtil.writeToStream(downloadFile, resp.getOutputStream());
    }

    void writeDownLoadStream(HttpServletResponse resp) throws IOException {
        String disposition = StrUtil.format("attachment;downloadFileName={}", URLUtil.encode(downloadFileName, "UTF-8"));
        resp.setHeader("Content-Disposition", disposition);
        resp.setHeader("Content-Type", "application/octet-stream");
        IoUtil.copy(downloadInputStream, resp.getOutputStream());
        IoUtil.close(downloadInputStream);
    }

    void writePreviewFile(HttpServletResponse resp) throws IOException {
        //resp.setHeader("Content-Type", "application/octet-stream");
        FileUtil.writeToStream(previewFile, resp.getOutputStream());
    }

    void writePreviewStream(HttpServletResponse resp) throws IOException {
        resp.setHeader("Content-Type", "application/octet-stream");
        IoUtil.copy(previewInputStream, resp.getOutputStream());
        IoUtil.close(previewInputStream);
    }

    void writeQrCode(HttpServletResponse resp) throws IOException {
        QrConfig qrConfig = new QrConfig();
        qrConfig.setWidth(qrCodeWidth);
        qrConfig.setHeight(qrCodeHeight);
        byte[] bytes = QrCodeUtil.generatePng(qrCodeURL, qrConfig);
        resp.getOutputStream().write(bytes);
    }


    void write(HttpServletRequest req, HttpServletResponse resp) throws MvcWriteException {
        // 重定向
        if (vp.startsWith("redirect:")) {
            vp = vp.replace("redirect:", "");
            // 暂不提供永久重定向
            try {
                resp.sendRedirect(vp);
            } catch (IOException e) {
                throw new MvcWriteException(e);
            }
            return;
        }

        // 转发
        if (vp.startsWith("forward:")) {
           /* // 将数据装入 req Attr
            if (data != null) {
                data.forEach(req::setAttribute);
            } else if (odata != null && odata instanceof Map) {
                ((Map<String, Object>) odata).forEach(req::setAttribute);
            }*/
            vp = vp.replace("forward:", "");
            try {
                // 转发的控制器方法上若存在View类型的参数, 则会注入
                req.setAttribute("_view", this);
                req.getRequestDispatcher(vp).forward(req, resp);
            } catch (Exception e) {
                throw new MvcWriteException(e);
            }
            return;
        }

        Engine engine = Engine.use("spring-mvc");
        File file = getFile(this.vp);
        if (file.exists()) {
            resp.setHeader("Content-Type", "text/html; charset=utf-8");
            ISource source = engine.getEngineConfig().getSourceFactory().getSource(engine.getBaseTemplatePath(), vp, engine.getEncoding());
            Template template = engine.getTemplate(source);
            Map data = null;
            if (this.data != null) {
                data = this.data;
            } else if (this.odata != null && odata instanceof Map) {
                data = (Map) this.odata;
            }
            String html = template.renderToString(data);
            try {
                resp.getWriter().write(html);
            } catch (IOException e) {
                throw new MvcWriteException(e);
            }
        } else {
            throw new MvcWriteException(this.vp + " 不存在, 请检查 \"webapp\" 下是否存在此文件");
        }
    }

    File getFile(String path) {
        // 资源目录
       /* String[] resourceDirs = {"src/main/webapp/web-inf", "src/main/WebRoot/web-inf"};

        for (String resourceDir : resourceDirs) {
            File file = new File(resourceDir, path);
            // 必须是文件而非目录
            if (file.exists() && file.isFile()) return file;
        }*/
        return new File("webapp/WEB-INF", path);
    }
}